/*
 * Galaxium Messenger
 * Copyright (C) 2007-2008 Ben Motmans <ben.motmans@gmail.com>
 * 
 * License: GNU General Public License (GPL)
 *
 * This program is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License as published by the Free
 * Software Foundation; either version 2 of the License, or (at your option)
 * any later version.
 *
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
 * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * for more details.
 *
 * You should have received a copy of the GNU General Public License along
 * with this program; if not, write to the Free Software Foundation, Inc.,
 * 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

using System;
using System.IO;
using System.Xml;
using System.Text;
using System.Collections.Generic;

using Galaxium.Gui;
using Galaxium.Protocol;

using Anculus.Core;

namespace Galaxium.Protocol.Irc
{
	public static class IrcProtocolHelper
	{
		public static string[] SplitMessage (string message, int size)
		{
			int len = (int)Math.Ceiling ((float)message.Length / (float)size);
			string[] chunks = new string[len];
			
			for (int i=0; i<len; i++)
				chunks[i] = message.Substring (i*size, size);
			return chunks;
		}
		
		public static bool IsValidNickname (RemoteServerSettings rss, string name)
		{
			if (String.IsNullOrEmpty (name))
				return false;
			
			return name.Length <= rss.NickLength;
		}
		
		public static bool IsValidChannelName (RemoteServerSettings rss, string name)
		{
			if (String.IsNullOrEmpty (name))
				return false;
			
			char c = name[0];
			bool validPrefix = false;
			foreach (char prefix in rss.ChannelTypes) {
				if (c == prefix) {
					validPrefix = true;
					break;
				}
			}
			
			return validPrefix && name.Length > 1;
		}
		
		[System.Diagnostics.DebuggerHidden]
		public static void ThrowIfInvalidNickname (RemoteServerSettings rss, string name)
		{
			if (!IsValidNickname (rss, name))
				throw new ArgumentException ("Invalid nickname '" + name + '"');
		}
		
		[System.Diagnostics.DebuggerHidden]
		public static void ThrowIfInvalidChannelName (RemoteServerSettings rss, string name)
		{
			if (!IsValidChannelName (rss, name))
				throw new ArgumentException ("Invalid channel '" + name + '"');
		}
		
		public static IEnumerable<IrcNetworkInfo> GetAllStoredNetworkInfo ()
		{
			Stream stream = typeof (IrcProtocolHelper).Assembly.GetManifestResourceStream ("irc_servers.xml");
			
			XmlDocument doc = new XmlDocument ();
			doc.Load (stream);
			
			XmlElement root = doc.DocumentElement;
			foreach (XmlNode node in root.ChildNodes) {
				if (node.NodeType != XmlNodeType.Element || node.Name != "Network")
					continue;

				IrcNetworkInfo network = ReadStoredNetworkInfo (node as XmlElement);
				yield return network;
			}

			stream.Close ();
		}
		
		public static IrcNetworkInfo GetStoredNetworkInfo (string network)
		{
			Stream stream = typeof (IrcProtocolHelper).Assembly.GetManifestResourceStream ("irc_servers.xml");
			
			XmlDocument doc = new XmlDocument ();
			doc.Load (stream);
			
			XmlElement root = doc.DocumentElement;
			foreach (XmlNode node in root.ChildNodes) {
				if (node.NodeType != XmlNodeType.Element || node.Name != "Network")
					continue;
				
				XmlElement networkElement = node as XmlElement;
				string name = networkElement.GetAttribute ("name");
				if (String.Compare (name, network, true) != 0)
					continue;
				
				IrcNetworkInfo info = ReadStoredNetworkInfo (networkElement);
				return info;
			}

			stream.Close ();
			
			return null;
		}
		
		public static IrcNetworkInfo ReadStoredNetworkInfo (XmlElement networkElement)
		{
			string name = networkElement.GetAttribute ("name");
			
			IrcNetworkInfo network = new IrcNetworkInfo (name);
			List<IrcServerInfo> mirrors = new List<IrcServerInfo> ();
			foreach (XmlNode subNode in networkElement.ChildNodes) {
				if (subNode.NodeType != XmlNodeType.Element || subNode.Name != "Mirror")
					continue;
				
				XmlElement mirrorElement = subNode as XmlElement;
				int port = int.Parse (mirrorElement.GetAttribute ("port"));
				IrcServerInfo mirror = new IrcServerInfo (mirrorElement.GetAttribute ("hostname"), port);
				mirrors.Add (mirror);
			}
			
			network.Mirrors = mirrors.ToArray ();
			return network;
		}
		
		private static bool ParseModeFlags (string str, char[] modeChars, int[] modeValues, out bool give, out int[] flags)
		{
			List<int> parsed = new List<int> ();
			
			give = false;

			bool giveSet = false;
			bool invalidItem = false;
			
			int len = str.Length;
			int modeLen = modeChars.Length;
			
			for (int i=0; i<len; i++) {
				switch (str[i]) {
				case '-':
					if (giveSet)
						invalidItem = true;
					
					give = false;
					giveSet = true;
					break;
				case '+':
					if (giveSet)
						invalidItem = true;
					
					give = true;
					giveSet = true;
					break;
				default:
					bool added = false;
					for (int j=0; j<modeLen; j++)
					{
						if (str[i] == modeChars[j])
						{
							added = true;
							parsed.Add (modeValues[j]);
							break;
						}
					}
					
					if (!added)
							invalidItem = true;
					break;
				}
			}
			
			flags = parsed.ToArray ();
			
			if (invalidItem || !giveSet)
				return false;
			else
				return true;
		}
		
		public static bool ParseChannelModeFlags (string str, out bool give, out ChannelModeFlags[] flags)
		{
			string modeChars = "psitnmlbekvhoOaqrI";
			int[] modeValues = new int[] {
				(int)ChannelModeFlags.PrivateChannel, //p
				(int)ChannelModeFlags.SecretChannel, //s
				(int)ChannelModeFlags.InviteOnlyChannel, //i
				(int)ChannelModeFlags.OnlyOperChangeTopic, //t
				(int)ChannelModeFlags.NoExternalMessages, //n
				(int)ChannelModeFlags.Moderated, //m
				(int)ChannelModeFlags.UserLimit, //l
				(int)ChannelModeFlags.BanMask, //b
				(int)ChannelModeFlags.BanException, //e
				(int)ChannelModeFlags.ChannelKey, //k
				
				(int)ChannelModeFlags.Voice, //v
				(int)ChannelModeFlags.HalfOperator, //h
				(int)ChannelModeFlags.Operator, //o
				(int)ChannelModeFlags.ChannelCreator, //O

				(int)ChannelModeFlags.AnonymousChannel, //a
				(int)ChannelModeFlags.QuietChannel, //q
				(int)ChannelModeFlags.ServerReopChannel, //r
				(int)ChannelModeFlags.InvitationMask //I
			};
			
			int[] uncastedFlags = null;
			bool ret = ParseModeFlags (str, modeChars.ToCharArray (), modeValues, out give, out uncastedFlags);
			
			flags = new ChannelModeFlags[uncastedFlags.Length];
			for (int i=0; i<uncastedFlags.Length; i++)
				flags[i] = (ChannelModeFlags)uncastedFlags[i];
			
			return ret;
		}
		
		public static bool ParseUserModeFlags (string str, out bool give, out UserModeFlags[] flags)
		{
			string modeChars = "iswoarO";
			int[] modeValues = new int[] {
				(int)UserModeFlags.Invisible, //i
				(int)UserModeFlags.ReceiveServerNotices, //s
				(int)UserModeFlags.ReceiveWallops, //w
				(int)UserModeFlags.Operator, //o
		
				(int)UserModeFlags.Away, //a
				(int)UserModeFlags.RestrictedConnection, //r
				(int)UserModeFlags.LocalOperator //O
			};
			
			int[] uncastedFlags = null;
			bool ret = ParseModeFlags (str, modeChars.ToCharArray (), modeValues, out give, out uncastedFlags);
			
			flags = new UserModeFlags[uncastedFlags.Length];
			for (int i=0; i<uncastedFlags.Length; i++)
				flags[i] = (UserModeFlags)uncastedFlags[i];
			
			return ret;
		}
		
		public static ChannelModeFlags GetChannelUserModeFlagsFromToken (char token)
		{
			switch (token) {
			case '@':
				return ChannelModeFlags.Operator;
			case '+':
				return ChannelModeFlags.Voice;
			case '!':
				return ChannelModeFlags.ChannelCreator;
			case '%':
				return ChannelModeFlags.HalfOperator;
			default:
				return ChannelModeFlags.None;
			}
		}
		
		public static string GetChannelUserModeString (ChannelModeFlags flags)
		{
			if ((flags & ChannelModeFlags.ChannelCreator) == ChannelModeFlags.ChannelCreator)
				return "!";
			else if ((flags & ChannelModeFlags.Operator) == ChannelModeFlags.Operator)
				return "@";
			else if ((flags & ChannelModeFlags.HalfOperator) == ChannelModeFlags.HalfOperator)
				return "%";
			else if ((flags & ChannelModeFlags.Voice) == ChannelModeFlags.Voice)
				return "+";
			else
				return String.Empty;
		}
		
		public static string GetChannelModeString (ChannelModeFlags flags)
		{
			string strFinal = String.Empty;
			
			if ((flags & ChannelModeFlags.Voice) == ChannelModeFlags.Voice)
				strFinal += "v";
			if ((flags & ChannelModeFlags.Moderated) == ChannelModeFlags.Moderated)
				strFinal += "m";
			if ((flags & ChannelModeFlags.PrivateChannel) == ChannelModeFlags.PrivateChannel)
				strFinal += "p";
			if ((flags & ChannelModeFlags.SecretChannel) == ChannelModeFlags.SecretChannel)
				strFinal += "s";
			if ((flags & ChannelModeFlags.InviteOnlyChannel) == ChannelModeFlags.InviteOnlyChannel)
				strFinal += "i";
			if ((flags & ChannelModeFlags.NoExternalMessages) == ChannelModeFlags.NoExternalMessages)
				strFinal += "n";
			if ((flags & ChannelModeFlags.OnlyOperChangeTopic) == ChannelModeFlags.OnlyOperChangeTopic)
				strFinal += "t";
			if ((flags & ChannelModeFlags.Operator) == ChannelModeFlags.Operator)
				strFinal += "o";
			
			return strFinal;
		}
		
		public static IEnumerable<IMessageChunk> ParseMessage (string message)
		{
			int len = message.Length;
			
			bool bold = false;
			bool underline = false;
			int fgColor = -1;
			int bgColor = -1;
			
			StringBuilder sb = new StringBuilder ();
			
			for (int i=0; i<len; i++) {
				char c = message[i];
				
				switch (c) {
				case IrcConstants.Markers.Bold:
					foreach (IMessageChunk chunk in CreateTextChunks (sb, bold, underline, fgColor, bgColor))
						yield return chunk;

					bold = !bold;
					break;
				case IrcConstants.Markers.Underline:
					foreach (IMessageChunk chunk in CreateTextChunks (sb, bold, underline, fgColor, bgColor))
						yield return chunk;

					underline = !underline;
					break;
				case IrcConstants.Markers.Color:
					foreach (IMessageChunk chunk in CreateTextChunks (sb, bold, underline, fgColor, bgColor))
						yield return chunk;

					fgColor = -1;
					bgColor = -1;
					
					//don't use a stringbuilder since these strings are max 2 chars long
					string fgStr = String.Empty;
					string bgStr = String.Empty;

					bool foundComma = false;
					int j=(i+1);
					for (; j<5; j++) { //look ahead max 5 chars to find the colors
						char cc = message[j];
						
						if (char.IsDigit (cc)) {
							if (foundComma)
								bgStr += cc;
							else
								fgStr += cc;
						} else if (cc == ',') {
							if (!foundComma)
								foundComma = true;
							else
								break;
						} else {
							break;
						}
					}
					
					if (int.TryParse (fgStr, out fgColor))
						fgColor = GetColor (fgColor);

					if (int.TryParse (bgStr, out bgColor))
						bgColor = GetColor (bgColor);
					
					// This is because we seem to be losing the char after a color code.
					i = j-1; //skip ahead
					break;
				case IrcConstants.Markers.Reverse:
					foreach (IMessageChunk chunk in CreateTextChunks (sb, bold, underline, fgColor, bgColor))
						yield return chunk;

					fgColor = -1;
					bgColor = -1;
					break;
				case IrcConstants.Markers.Reset:
					foreach (IMessageChunk chunk in CreateTextChunks (sb, bold, underline, fgColor, bgColor))
						yield return chunk;

					bold = false;
					underline = false;
					fgColor = -1;
					bgColor = -1;
					break;
				default:
					sb.Append (c);
					break;
				}
			}
			
			foreach (IMessageChunk chunk in CreateTextChunks (sb, bold, underline, fgColor, bgColor))
						yield return chunk;
		}
		
		public static IEnumerable<IMessageChunk> CreateTextChunks (StringBuilder sb, bool bold, bool underline, int fgColor, int bgColor)
		{
			if (sb.Length == 0)
				yield break;
			
			IMessageStyle style = new MessageStyle ();
			style.Bold = bold;
			style.Underline = underline;
			
			if (fgColor >= 0)
				style.Foreground = (ARGBColor)fgColor;
			//TODO: set the bg color
			
			string text = sb.ToString ();
			
			if (text.Length > 6) { //the string is long enough to contain links
				foreach (IMessageChunk chunk in MessageUtility.ParseHyperlinks (text, style))
					yield return chunk;
			} else {
				yield return new TextMessageChunk (null, style, text);
			}
			
			sb.Remove (0, sb.Length);
		}
		
		public static int GetColor (int colorId)
		{
			colorId = colorId % 16;
			
			switch (colorId) {
			case 0: return IrcConstants.Colors.White;
			case 1: return IrcConstants.Colors.Black;
			case 2: return IrcConstants.Colors.Blue;
			case 3: return IrcConstants.Colors.Green;
			case 4: return IrcConstants.Colors.LightRed;
			case 5: return IrcConstants.Colors.Brown;
			case 6: return IrcConstants.Colors.Purple;
			case 7: return IrcConstants.Colors.Orange;
			case 8: return IrcConstants.Colors.Yellow;
			case 9: return IrcConstants.Colors.LightGreen;
			case 10: return IrcConstants.Colors.Cyan;
			case 11: return IrcConstants.Colors.LightCyan;
			case 12: return IrcConstants.Colors.LightBlue;
			case 13: return IrcConstants.Colors.Pink;
			case 14: return IrcConstants.Colors.Grey;
			case 15: return IrcConstants.Colors.LightGrey;
			default: return IrcConstants.Colors.Black;
			}
		}
		
		public static string ConvertLongToIPAddress (string address)
		{
			long l;
			if (!long.TryParse (address, out l))
				return null;

			byte[] blocks = BitConverter.GetBytes (l);
			
			StringBuilder sb = new StringBuilder ();
			sb.Append (blocks[3]);
			sb.Append ('.');
			sb.Append (blocks[2]);
			sb.Append ('.');
			sb.Append (blocks[1]);
			sb.Append ('.');
			sb.Append (blocks[0]);
			
			return sb.ToString ();
		}
		
		public static string EncodeDccFilename (string filename)
		{
			filename = Path.GetFileName (filename); //strip path info
			
			if (filename.Contains (" "))
				return String.Concat ("\"", filename, "\"");
			else
				return filename;
		}
		
		public static string DecodeDccFilename (string filename)
		{
			if (filename.Length > 2 && filename.StartsWith ("\"")) //the filename is encapsulated in quotes
				return filename.Substring (1, filename.Length - 2);
			return filename;
		}
	}
}
